﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework.Graphics;

namespace Ratatoskr
{
    public abstract class ParentEntity
    {

        #region Fields & Properties

        public abstract GraphicsDevice GraphicsDevice { get; }

        protected bool _initialized;
        /// <summary>
        /// Initialization status.
        /// </summary>
        public bool Initialized
        {
            get { return _initialized; }
        }

        protected List<Entity> _nodes = new List<Entity>();
        /// <summary>
        /// References all entities attached to this parent, whether named or anonymous.
        /// </summary>
        public Entity[] Nodes
        {
            get { return _nodes.ToArray<Entity>(); }
        }

        protected Dictionary<string, Entity> _children = new Dictionary<string, Entity>();
        /// <summary>
        /// References all named entities attached to this parent.
        /// </summary>
        public string[] Children
        {
            get { return _children.Keys.ToArray<string>(); }
        }

        protected Dictionary<string, Component> _components = new Dictionary<string, Component>();
        /// <summary>
        /// References all components attached to this parent.
        /// </summary>
        public string[] Components
        {
            get { return _components.Keys.ToArray<string>(); }
        }

        #endregion

        #region Constructor & Initialization

        /// <summary>
        /// Create a new ParentEntity with the specified submembers. No initialization is performed.
        /// </summary>
        /// <param name="components">Components to attach, or null.</param>
        /// <param name="children">Named entities to attach, or null.</param>
        /// <param name="nodes">Anonymous entities to attach, or null.</param>
        public ParentEntity()
        {
        }

        /// <summary>
        /// If not currently initialized, initialize all submembers then initialize self.
        /// </summary>
        /// <returns>True if all members initialized successfully, false otherwise.</returns>
        public bool Initialize()
        {
            if (!_initialized)
                _Initialize();
            return _initialized;
        }

        protected void _Initialize()
        {
            _initialized = true;
            ExtendInitializePre();
            if (_initialized)
                foreach (Component c in _components.Values)
                    if (!c.Initialize())
                        _initialized = false;
            if (_initialized)
                foreach (Entity e in _nodes)
                    if (!e.Initialize())
                        _initialized = false;
            if (_initialized)
                ExtendInitializePost();
        }

        #endregion

        #region Child & Component Queries

        public bool HasChild(string name)
        {
            try { return _children.ContainsKey(name); }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Child request key was null.", e); }
        }

        public Entity GetChild(string name)
        {
            try { return _children[name]; }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Child request key was null.", e); }
            catch (KeyNotFoundException e) { throw new KeyNotFoundException("Requested child was not found.", e); }
        }

        public bool TryGetChild(string name, out Entity child)
        {
            try { return _children.TryGetValue(name, out child); }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Component request key was null.", e); }
        }

        public bool TryGetChild<T>(string name, out T child) where T : Entity
        {
            Entity c;
            try
            {
                if (!_children.TryGetValue(name, out c))
                {
                    child = null;
                    return false;
                }
            }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Component request key was null.", e); }
            child = c as T;
            if (child == null)
                return false;
            return true;
        }

        public bool HasComponent(string name)
        {
            try { return _components.ContainsKey(name); }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Component request key was null.", e); }
        }

        public Component GetComponent(string name)
        {
            try { return _components[name]; }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Component request key was null.", e); }
            catch (KeyNotFoundException e) { throw new KeyNotFoundException("Requested component was not found.", e); }
        }

        public bool TryGetComponent(string name, out Component component)
        {
            try { return _components.TryGetValue(name, out component); }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Component request key was null.", e); }
        }

        public bool TryGetComponent<T>(string name, out T component) where T : Component
        {
            Component c;
            try
            {
                if (!_components.TryGetValue(name, out c))
                {
                    component = null;
                    return false;
                }
            }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Component request key was null.", e); }
            component = c as T;
            if (component == null)
                return false;
            return true;
        }

        #endregion

        #region Hierarchy Helper Methods

        protected internal bool Register(Entity e, bool child)
        {
            if (e == null)
                throw new ArgumentNullException("Attempted to register a null node.");
            if (child)
                try
                {
                    if (_children.ContainsKey(e.Name))
                        return false;
                    else _children[e.Name] = e;
                }
                catch (ArgumentNullException ex) { throw new ArgumentNullException("Attempted to register a child with no name.", ex); }
            else if (_nodes.Contains(e))
                return false;
            _nodes.Add(e);
            return true;
        }

        protected internal bool Register(Component c)
        {
            if (c == null)
                throw new ArgumentNullException("Attempted to register a null component.");
            if (_components.ContainsKey(c.Type))
                return false;
            _components[c.Type] = c;
            return true;
        }

        protected internal void Sever(Entity e)
        {
            if (e == null)
                throw new ArgumentNullException("Attempted to sever a null node.");
            _nodes.Remove(e);
            if (e.Name != null)
                if (_children.ContainsKey(e.Name))
                    if (_children[e.Name] == e)
                        _children.Remove(e.Name);
        }

        protected internal void Sever(Component c)
        {
            if (c == null)
                throw new ArgumentNullException("Attempted to sever a null component.");
            _components.Remove(c.Type);
        }

        #endregion

        #region ParentChanged Event (for subclasses)

        /// <summary>
        /// Subclasses with parents use this event to notify their children of inherited hierarchy changes.
        /// </summary>
        public event EventHandler ParentChanged;

        /// <summary>
        /// Trigger this object's ParentChanged event.
        /// </summary>
        /// <param name="e">EventArgs</param>
        protected virtual void OnParentChanged(object sender, EventArgs e)
        {
            EventHandler handler = ParentChanged;
            if (handler != null)
                handler(sender, e);
        }

        #endregion

        #region Virtual Methods & Implementation Notes

        /// <summary>
        /// Called by Initialize() after verifying that this entity's state allows initialization and setting _initialized to true, before any other operations.
        /// </summary>
        protected virtual void ExtendInitializePre() { }

        /// <summary>
        /// Called by Initialize() after successfully initializing submembers. There are no subsequent operations.
        /// </summary>
        protected virtual void ExtendInitializePost() { }

        #endregion

    }

    public class RootEntity : ParentEntity
    {
        protected GraphicsDevice _graphicsDevice;
        public override GraphicsDevice GraphicsDevice
        {
            get { return _graphicsDevice; }
        }

        public RootEntity(GraphicsDevice graphicsDevice)
        {
            _graphicsDevice = graphicsDevice;
        }
    }
}
